import music21
import utils
import copy


class TemplateException(Exception):
    pass


class Template():
    def __init__(self):
        pass

    def setStream(self,stream):
        self.stream = stream.__iter__()

    def sliceStream(self,stream,slice_):
        # Keep all the parts
        # Keep the smallest number of measures that
        # yields at least 'slice_' notes in at least one part
        nb_measures_to_keep = False
        for p in stream:
            if isinstance(p,music21.stream.Part):
                needed_measures=0
                notes_in_part=0
                for m in p:
                    if isinstance(m,music21.stream.Measure):
                        needed_measures+=1
                        for n in m:
                            if isinstance(n,music21.note.Note):
                                notes_in_part+=1
                                if notes_in_part==slice_:
                                    if not nb_measures_to_keep or \
                                    needed_measures<nb_measures_to_keep:
                                        nb_measures_to_keep = needed_measures

        # Remove unneeded measures in all parts
        for p in stream:
            if isinstance(p,music21.stream.Part):
                measures_to_remove = [m for m in p if isinstance(m,music21.stream.Measure) and p.index(m)>=nb_measures_to_keep]
                map(lambda x: p.remove(x),measures_to_remove)


# TODO: implement a slice function that makes sense according to the sequence length
class SequenceTemplate(Template):
    def __init__(self,sequence_length=2):
        self.sequence_length=sequence_length

    def __iter__(self):
        # Initialize iterators
        try:
            self.stream
        except:
            raise TemplateException("There is no stream to iterate")


        while True:
            p = self.stream.next()
            if isinstance(p,music21.stream.Part):
                self.part = p.__iter__()
                while True:
                    m = self.part.next()
                    if isinstance(m,music21.stream.Measure):
                        self.measure = m.__iter__()
                        break
                break

        # Accumulate notes
        self.notes=[]

        return self

    def next(self):
        try:
            # First select a sequence of 'sequence_length' notes
            # FIXME: what about ties?

            # Remove one note, i.e. 'roll' the sequence
            if len(self.notes)>=2:
                self.notes.pop(0)

            # Add enough notes
            while True:
                n = self.measure.next()
                if isinstance(n,music21.note.Note):
                    self.notes.append(n)
                if len(self.notes) == self.sequence_length:
                    return utils.reverse(self.notes)

        except StopIteration:
            try:
                while True:
                    m = self.part.next()
                    if isinstance(m,music21.stream.Measure):
                        self.measure = m.__iter__()
                        return self.next()
            except StopIteration:
                while True:
                    p = self.stream.next()
                    if isinstance(p,music21.stream.Part):
                        self.part = p.__iter__()
                        while True:
                            m = self.part.next()
                            if isinstance(m,music21.stream.Measure):
                                self.measure = m.__iter__()
                                break
                        self.notes=[]
                        return self.next()


class NoteTemplate(Template):
    def __init__(self):
        pass

    def __iter__(self):
        # Initialize iterators
        try:
            self.stream
        except:
            raise TemplateException("There is no stream to iterate")

        for p in self.stream:
            if isinstance(p,music21.stream.Part):
                self.part = p.__iter__()
                break

        for m in self.part:
            if isinstance(m,music21.stream.Measure):
                self.measure = m.__iter__()
                break


        return self

    def next(self):
        try:
            while True:
                n = self.measure.next()
                if isinstance(n,music21.note.Note):
                    return n
        except StopIteration:
            try:
                while True:
                    m = self.part.next()
                    if isinstance(m,music21.stream.Measure):
                        self.measure = m.__iter__()
                        return self.next()
            except StopIteration:
                while True:
                    p = self.stream.next()
                    if isinstance(p,music21.stream.Part):
                        self.part = p.__iter__()
                        while True:
                            m = self.part.next()
                            if isinstance(m,music21.stream.Measure):
                                self.measure = m.__iter__()
                                return self.next()

    def __repr__(self):
        return "Note"



        









